package com.swipemaster.moves;

import com.swipemaster.common.Global;
import com.swipemaster.common.Const;
import com.swipemaster.common.Coords;
import com.swipemaster.elements.Circle;
import com.swipemaster.elements.Element;
import com.swipemaster.elements.Rectangle;
import com.swipemaster.levels.Level;

public class FreeMove extends Move
{
	/** Magic number used to help calculate speed of particles (adjusted to the device screen size). */
	private static final double PARTICLES_MULTIPLIER = (9 * 980) / Global.app.consts.diagonal;
	/** Speed in pixels per ms. */
	private double speedX, speedY;
	/** Whether we collide with walls or not. */
	private boolean wallCollision = true;
	/** If particles are on. */
	private boolean particlesActive = true;
	/** Point of collision between bouncing elements. */
	private int collisionX, collisionY;
	/** level in which there is element with this move */
	private final Level level;

	/** @param speedX Universal units.
	 * @param speedY Universal units. */
	public FreeMove(double speedX, double speedY, Level level)
	{
		this.speedX = Coords.mulByDiag(speedX);
		this.speedY = Coords.mulByDiag(speedY);
		this.level = level;
	}

	/** Compute new vectors after bounce between free moving elements.
	 * @param thisElement First element.
	 * @param otherElement Second element
	 * @return True, if bouncing occurred. There were 2 free moving elements. */
	public boolean bounce(Element thisElement, Element otherElement)
	{
		if ((thisElement.move instanceof FreeMove) == false)
		{
			return false;
		}
		if ((otherElement.move instanceof FreeMove) == false)
		{
			return false;
		}
		final FreeMove otherMove = (FreeMove) otherElement.move;

		double unx, uny;
		double utx, uty;
		double v1x, v1y;
		double v2x, v2y;

		// normal vector
		unx = otherElement.x - thisElement.x;
		uny = otherElement.y - thisElement.y;

		// unit normal vector
		final double length = Math.sqrt((unx * unx) + (uny * uny));
		if (length != 0)
		{
			unx = unx / length;
			uny = uny / length;
		}
		else
		{
			unx = 0;
			uny = 0;
		}

		// unit tangent vector
		utx = -uny;
		uty = unx;

		// velocity vectors
		v1x = speedX;
		v1y = speedY;
		v2x = otherMove.speedX;
		v2y = otherMove.speedY;

		// scalar values of velocity in each axis
		final double v1n = (unx * v1x) + (uny * v1y);
		final double v1t = (utx * v1x) + (uty * v1y);
		final double v2n = (unx * v2x) + (uny * v2y);
		final double v2t = (utx * v2x) + (uty * v2y);

		// new values of velocity
		final double v1nAfter = ((v1n * (thisElement.mass - otherElement.mass)) + (2 * otherElement.mass * v2n))
				/ (thisElement.mass + otherElement.mass);
		final double v2nAfter = ((v2n * (otherElement.mass - thisElement.mass)) + (2 * thisElement.mass * v1n))
				/ (thisElement.mass + otherElement.mass);

		// new velocity vectors
		v1x = (unx * v1nAfter) + (utx * v1t);
		v1y = (uny * v1nAfter) + (uty * v1t);
		v2x = (unx * v2nAfter) + (utx * v2t);
		v2y = (uny * v2nAfter) + (uty * v2t);

		speedX = v1x;
		speedY = v1y;

		otherMove.speedX = v2x;
		otherMove.speedY = v2y;

		if (particlesActive)
		{
			computeCollisionCords(thisElement, otherElement);
			level.computeParticles(collisionX, collisionY, Const.CENTRAL, howManyParticles(this, otherMove));
		}

		return true;
	}

	@Override
	public void calculateNextPosition(Element element, int interval)
	{
		int dx = (int) (speedX * interval);
		if (dx == 0)
		{
			if (speedX > 0)
			{
				dx = 1;
			}
			else
			{
				dx = -1;
			}
		}
		nextX = element.x + dx;

		int dy = (int) (speedY * interval);
		if (dy == 0)
		{
			if (speedY > 0)
			{
				dy = 1;
			}
			else
			{
				dy = -1;
			}
		}
		nextY = element.y + dy;

		wallCollision(element);
	}

	public void setParticlesActive(boolean particlesActive)
	{
		this.particlesActive = particlesActive;
	}

	public void setWallCollision(boolean wallCollision)
	{
		this.wallCollision = wallCollision;
	}

	@Override
	public void speedUp(double scale)
	{
		speedX *= scale;
		speedY *= scale;
	}

	public void wallCollision(Element element)
	{
		if (wallCollision)
		{
			if (element.isWallCollision(Const.LEFT, true) && (speedX < 0))
			{
				speedX = -speedX;
				if (particlesActive)
				{
					level.computeParticles(0, nextY, Const.RIGHT, howManyParticles(this));
				}
			}
			if (element.isWallCollision(Const.RIGHT, true) && (speedX > 0))
			{
				speedX = -speedX;
				if (particlesActive)
				{
					level.computeParticles(Global.app.consts.width, nextY, Const.LEFT, howManyParticles(this));
				}
			}
			if (element.isWallCollision(Const.UP, true) && (speedY < 0))
			{
				speedY = -speedY;
				if (particlesActive)
				{
					level.computeParticles(nextX, 0, Const.DOWN, howManyParticles(this));
				}
			}
			if (element.isWallCollision(Const.DOWN, true) && (speedY > 0))
			{
				speedY = -speedY;
				if (particlesActive)
				{
					level.computeParticles(nextX, Global.app.consts.height, Const.UP, howManyParticles(this));
				}
			}
		}
		else
		{
			int width = 0, height = 0;
			if (element instanceof Circle)
			{
				width = height = ((Circle) element).radius;
			}
			else if (element instanceof Rectangle)
			{
				width = ((Rectangle) element).width / 2;
				height = ((Rectangle) element).height / 2;
			}
			if (element.isWallCollision(Const.LEFT, false) && (speedX < 0))
			{
				nextX = Global.app.consts.width + width;
			}
			if (element.isWallCollision(Const.RIGHT, false) && (speedX > 0))
			{
				nextX = -width;
			}
			if (element.isWallCollision(Const.UP, false) && (speedY < 0))
			{
				nextY = Global.app.consts.height + height;
			}
			if (element.isWallCollision(Const.DOWN, false) && (speedY > 0))
			{
				nextY = -height;
			}
		}

	}

	private void computeCollisionCords(Element thisElement, Element otherElement)
	{
		if (thisElement instanceof Circle)
		{
			final Circle c = (Circle) thisElement;
			collisionX = thisElement.x
					+ (int) ((c.radius * (double) (otherElement.x - thisElement.x)) / Coords.distance(thisElement.x,
							thisElement.y, otherElement.x, otherElement.y));
			collisionY = thisElement.y
					+ (int) ((c.radius * (double) (otherElement.y - thisElement.y)) / Coords.distance(thisElement.x,
							thisElement.y, otherElement.x, otherElement.y));
			return;
		}

		if (otherElement instanceof Circle)
		{
			final Circle c = (Circle) otherElement;
			collisionX = otherElement.x
					+ (int) ((c.radius * (double) (thisElement.x - otherElement.x)) / Coords.distance(thisElement.x,
							thisElement.y, otherElement.x, otherElement.y));
			collisionY = otherElement.y
					+ (int) ((c.radius * (double) (thisElement.y - otherElement.y)) / Coords.distance(thisElement.x,
							thisElement.y, otherElement.x, otherElement.y));
			return;
		}

		final Rectangle r1 = (Rectangle) thisElement;
		final Rectangle r2 = (Rectangle) otherElement;
		collisionX = r1.x + (int) (((r1.width / 2) * (double) (r2.x - r1.x)) / Coords.distance(r1.x, r1.y, r2.x, r2.y));
		collisionY = r1.y
				+ (int) (((r1.height / 2) * (double) (r2.y - r1.y)) / Coords.distance(r1.x, r1.y, r2.x, r2.y));
	}

	private int howManyParticles(FreeMove thisMove)
	{
		int howManyParticles = 0;
		double sumSpeed = Math.abs(speedX) + Math.abs(speedY);
		sumSpeed *= PARTICLES_MULTIPLIER;
		howManyParticles = (int) sumSpeed;
		return howManyParticles;
	}

	private int howManyParticles(FreeMove thisMove, FreeMove otherMove)
	{
		int howManyParticles = 0;
		double sumSpeed = Math.abs(thisMove.speedX - otherMove.speedX) + Math.abs(thisMove.speedY - otherMove.speedY);
		sumSpeed *= PARTICLES_MULTIPLIER;
		howManyParticles = (int) sumSpeed;
		return howManyParticles;
	}

}
